package vcp.client.GUI.mvc.controller;

import java.awt.BorderLayout;

import javax.swing.JLayer;

import org.joda.time.DateTime;

import vcp.client.GUI.events.ConnectionRequestEvent;
import vcp.client.GUI.factories.MVCFactory;
import vcp.client.GUI.mvc.model.UIRootModel;
import vcp.client.GUI.mvc.view.UIAbstractView;
import vcp.client.GUI.mvc.view.UIDemoView;
import vcp.client.GUI.mvc.view.UIRootView;
import vcp.client.GUI.mvc.view.effects.WaitLayerUI;
import vcp.client.exceptions.ReplaceMVCException;
import vcp.client.interfaces.delegates.IApplicationWindowDelegate;
import vcp.client.interfaces.delegates.IControllerModelDelegate;
import vcp.client.interfaces.delegates.IControllerViewDelegate;
import vcp.client.interfaces.delegates.IParentControllerDelegate;
import vcp.client.interfaces.listeners.IConnectionListener;
import vcp.client.utility.Common.UserNotification;
import vcp.client.utility.UtilityHelper;
import vcp.enums.PopUpMessageType;
import vcp.enums.Screen;

/**
 * 
 * @author VCP team
 * 
 *         <p>
 *         Main controller which, manages all application's activity
 *         </p>
 *         responsible <b> <u>only</u> </b> for the top level management
 */
public class UIRootController implements IControllerModelDelegate, IControllerViewDelegate, IParentControllerDelegate, IApplicationWindowDelegate, IConnectionListener
{
	public static boolean DEBUG = true;

	/**
	 * Application main GUI frame
	 */
	private final UIRootView view;

	/**
	 * Application main data source
	 */
	private final UIRootModel model;

	/**
	 * Current on screen MVC
	 */
	private UIAbstractController currentMVC;
	/**
	 * @see JLayer <p>
	 *      layer of view
	 *      </p>
	 */
	private final JLayer<UIAbstractView> layer;

	/**
	 * wait layer for UI
	 */
	private final WaitLayerUI waitLayerUI;
	/**
	 * client lock the system
	 */
	private final Object clientTimeLock;
	/**
	 * time of client aper
	 */
	private DateTime clientTime;

	public DateTime getClientTime()
	{
		DateTime currentTime;
		synchronized (this.clientTimeLock)
		{
			currentTime = this.clientTime;
		}
		return currentTime;
	}

	public void setClientTime(DateTime clientTime)
	{
		synchronized (this.clientTimeLock)
		{
			this.clientTime = clientTime;
		}
	}

	/**
	 * <p>
	 * Responsible to give the application controller the next MVC requested by
	 * user.
	 * </p>
	 * <b>Decouples</b> creation of MVCs from application controller
	 */
	private final MVCFactory factoryController;

	/**
	 * 
	 * @param model
	 *            : Main data source
	 * @param view
	 *            : Main GUI frame
	 */
	public UIRootController(UIRootModel model, UIRootView view)
	{
		this.clientTimeLock = new Object();
		this.model = model;
		this.view = view;

		this.layer = new JLayer<>();
		this.waitLayerUI = new WaitLayerUI();

		this.factoryController = new MVCFactory(this.model);

		this.currentMVC = this.factoryController.createController(Screen.DEMO, this);
		((UIDemoView) this.currentMVC.getView()).setConnectionLister(this);

		this.addNewInnerView();
		this.setupListeners();

		this.view.setVisible(true);
	}

	// ****************************************************************************************************************************
	// Private methods ***
	// ****************************************************************************************************************************
	/**
	 * register all relevant listeners to perform callback
	 */
	private void setupListeners()
	{
		this.view.setControllerDelegate(this);
		this.model.setControllerDelegate(this);

		this.currentMVC.setParentControllerDelegate(this);

		this.view.setApplicationWindowDelegate(this);
		this.view.setControllerDelegate(this);

		this.model.setConnectionListener(this);
	}

	/**
	 * Tells the main controller to replace inner MVC
	 * 
	 * @param newMVC
	 *            : The new required MVC
	 */
	private void replaceMVC(UIAbstractController newMVC)
	{
		this.removeOldInnerView();

		this.view.removeComponentsFromToolBar(this.currentMVC.getAdditionalToolbarComponents());

		this.loadingStop();
		this.currentMVC = newMVC;

		this.view.addComponentsToToolBar(this.currentMVC.getAdditionalToolbarComponents());

		this.addNewInnerView();
	}

	/**
	 * Adding new inner <b><u>view</u></b> to main frame
	 */
	private void addNewInnerView()
	{
		UIAbstractView currentView = this.currentMVC.getView();
		this.layer.setView(currentView);
		this.layer.setUI(this.waitLayerUI);

		this.view.addView(this.layer, BorderLayout.CENTER);
		this.updateScreenName(this.currentMVC.getScreenName());
	}

	/**
	 * Removing old inner <b><u>view</u></b> to main frame
	 */
	private void removeOldInnerView()
	{
		this.view.removeView(this.currentMVC.getView());
		this.currentMVC.clearMVC();
	}

	/**
	 * extracting the components of MVC
	 * 
	 * @param newScreen
	 * @return created UIAbstractController
	 * @throws ReplaceMVCException
	 */
	private UIAbstractController extractMVC(Screen newScreen) throws ReplaceMVCException
	{
		UIAbstractController newMVC = this.factoryController.createController(newScreen, this);
		if (newMVC == null)
		{
			throw new ReplaceMVCException(String.format(UserNotification.UNABLE_TO_CREATE__NAME__SCREEN, newScreen.toString()));
		}

		return newMVC;
	}

	/**
	 * send the reason of connection lost failure
	 * 
	 * @param lostReason
	 */
	private void onConnectionLostReset(String lostReason)
	{
		String sessionTimeOutMsg = String.format("%s\n\n%s", UserNotification.SESSION_TIME_OUT, lostReason);
		this.showPopUpMessage(sessionTimeOutMsg, PopUpMessageType.ERROR);
		this.view.setToolBarConnectionDisplayLabelEnable(false);
		this.view.resetClientClock();
		try
		{
			UIDemoController demoMVC = (UIDemoController) this.extractMVC(Screen.DEMO);
			demoMVC.clearMVC();
			demoMVC.reset();
			this.replaceMVC(demoMVC);
		}
		catch (ReplaceMVCException rse)
		{
			this.showPopUpMessage(rse.getMessage(), PopUpMessageType.ERROR);
		}
	}

	// ****************************************************************************************************************************
	// IParentControllerDelegate methods ***
	// ****************************************************************************************************************************
	@Override
	public void updateScreenName(Screen name)
	{
		this.view.updateScreenName(name);
	}

	@Override
	public DateTime requestCurrentClientDateTime()
	{
		return this.getClientTime();
	}

	// ****************************************************************************************************************************
	// IMessage methods ***
	// ****************************************************************************************************************************
	@Override
	public void showPopUpMessage(String displayMessage, PopUpMessageType messageType)
	{
		switch (messageType)
		{
			case ERROR:
				this.view.showError(displayMessage);
				break;
			case WARNING:
				this.view.showWarning(displayMessage);
				break;
			case QUESTION:
				this.view.showQuestion(displayMessage);
				break;
			case INFORMATION:
				this.view.showInformation(displayMessage);
				break;
		}
	}

	// ****************************************************************************************************************************
	// IMessage methods ***
	// ****************************************************************************************************************************
	@Override
	public void onButtonPress(Object object)
	{
		Screen name = (Screen) object;
		this.loadMVC(name);
	}

	// ****************************************************************************************************************************
	// INotify methods ***
	// ****************************************************************************************************************************
	@Override
	public void onTimeChange(DateTime currentTime)
	{
		this.setClientTime(currentTime);
		this.view.updateCurrentTime(this.getClientTime());
		this.currentMVC.onTimeChange(currentTime);
	}

	@Override
	public void loadMVC(Screen newScreen)
	{
		try
		{
			UIAbstractController newMVC = this.extractMVC(newScreen);
			this.replaceMVC(newMVC);
		}
		catch (ReplaceMVCException exception)
		{
			this.showPopUpMessage(exception.getMessage(), PopUpMessageType.ERROR);
			this.loadingStop();
		}
	}

	@Override
	public void loadMVCWithData(Screen newScreen, Object dataObject)
	{
		try
		{
			UIAbstractController newMVC = this.extractMVC(newScreen);
			newMVC.uploadDataContext(dataObject);
			this.replaceMVC(newMVC);
		}
		catch (ReplaceMVCException exception)
		{
			this.showPopUpMessage(exception.getMessage(), PopUpMessageType.ERROR);
			this.loadingStop();
		}
	}

	@Override
	public void loadingStart()
	{
		this.waitLayerUI.start();
	}

	@Override
	public void loadingStop()
	{
		this.waitLayerUI.stop();
	}

	// ****************************************************************************************************************************
	// IControllerModelDelegate methods ***
	// ****************************************************************************************************************************
	/**
	 * Not implemented
	 */
	@Override
	public void updateControllerDelegateWithData(Object newData)
	{
	}

	/**
	 * Not implemented
	 */
	@Override
	public void notifyOnDataUpdate(Object data)
	{
	}

	// ****************************************************************************************************************************
	// IApplicationWindowDelegate methods ***
	// ****************************************************************************************************************************

	@Override
	public void onApplicationClose()
	{
		this.model.disconnectFromServer();
	}

	// ****************************************************************************************************************************
	// IConnectionListener methods ***
	// ****************************************************************************************************************************
	@Override
	public void onConnectionRequest(ConnectionRequestEvent connectionEvent)
	{
		String host = connectionEvent.getHost();
		Integer port = connectionEvent.getPort();

		if (UtilityHelper.getUtilityHelperInstance().isParamsNotNull(host, port))
		{
			boolean connected = this.model.connectToServer(host, port);
			if (connected)
			{
				this.view.setToolBarConnectionDisplayLabelEnable(true);
				this.currentMVC.getView().updateView();
			}
			else
			{
				this.showPopUpMessage(UserNotification.CONNECTION_TO_SERVER_FAIL, PopUpMessageType.ERROR);
			}
		}
	}

	@Override
	public void onConnectionLost(String lostReason)
	{
		this.onConnectionLostReset(lostReason);
	}

}
